In [1]:
import os
import pandas as pd
import plotly.express as px
import matplotlib.pyplot as plt
import seaborn as sns
from sqlalchemy import create_engine
from dotenv import load_dotenv
In [2]:
# === Chargement des variables d'environnement ===
load_dotenv()
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
DB_NAME = os.getenv("DB_NAME")
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD")
In [3]:
# === Connexion à la base PostgreSQL RDS ===
try:
    engine = create_engine(f"postgresql+psycopg2://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}")
    df = pd.read_sql("SELECT * FROM jobs", con=engine)
    print("✅ Données chargées depuis RDS avec succès")
except Exception as e:
    print("❌ Erreur de connexion à la base RDS :", e)
    df = pd.DataFrame()  # On crée un df vide pour éviter de planter la suite
✅ Données chargées depuis RDS avec succès
In [4]:
# Nettoyage de base
if not df.empty:
    df.dropna(subset=["title", "company", "location"], inplace=True)
    df["title"] = df["title"].str.lower().str.strip()
    df["company"] = df["company"].str.strip()
    df["location"] = df["location"].str.strip()

    # Aperçu des données
    print("\nAperçu des données :")
    print(df.head())
Aperçu des données :
   id                                   title                company  \
0   1          staff data scientist  featured  Mozilla  Remote-first   
1   2  associate cloud data scientist (m/f/d)              TD SYNNEX   
2   3         (usa) principal, data scientist                Walmart   
3   4    principal, data scientist – converse                Walmart   
4   5                          data scientist               Sun Life   

                                            location  salary_min  salary_max  \
0  Remote Canada\nRemote Canada\nFull Time\nSenio...         NaN         NaN   
1  Barcelona, Spain\nBarcelona, Spain\nFull Time\...         NaN         NaN   
2  (USA) Bentonville Global Tech …\n(USA) Bentonv...         NaN         NaN   
3  (USA) SUNNYVALE IV- 680 …\n(USA) SUNNYVALE IV-...         NaN         NaN   
4  Sun Life Global Solutions …\nSun Life Global S...         NaN         NaN   

  contract_type       source  
0          None  AI-jobs.net  
1          None  AI-jobs.net  
2          None  AI-jobs.net  
3          None  AI-jobs.net  
4          None  AI-jobs.net  
In [7]:
# === Visualisation 1 : Offres par ville ===
df_city = df["location"].value_counts().head(10).reset_index()
df_city.columns = ["city", "count"]
fig1 = px.bar(df_city, x="city", y="count", title="Top 10 des villes avec le plus d'offres")
fig1.show()
In [8]:
 # === Visualisation 2 : Entreprises qui recrutent le plus ===
df_company = df["company"].value_counts().head(10).reset_index()
df_company.columns = ["company", "count"]
fig2 = px.pie(df_company, names="company", values="count", title="Top entreprises qui recrutent")
fig2.show()
In [9]:
    # === Visualisation 3 : Distribution des salaires ===
    df_salaire = df.dropna(subset=["salary_min", "salary_max"])
    fig3 = px.box(df_salaire, y="salary_max", points="all", title="Distribution des salaires max")
    fig3.show()
In [11]:
    # === Visualisation 4 : Offres par source ===
    df_source = df["source"].value_counts().reset_index()
    df_source.columns = ["source", "count"]
    fig4 = px.pie(df_source, names="source", values="count", title="Origine des offres (API vs Scraping)")
    fig4.show()
In [12]:
    # === Visualisation 5 : Top intitulés de poste ===
    df_title = df["title"].value_counts().head(10).reset_index()
    df_title.columns = ["title", "count"]
    fig5 = px.bar(df_title, x="title", y="count", title="Top intitulés de poste")
    fig5.show()
In [14]:
    # === Visualisation 7 : Nombre d'offres avec ou sans salaire ===
    df["has_salary"] = df["salary_min"].notnull() & df["salary_max"].notnull()
    salary_counts = df["has_salary"].value_counts().rename({True: "Avec salaire", False: "Sans salaire"})
    fig7 = px.pie(values=salary_counts.values, names=salary_counts.index, title="Présence des salaires dans les offres")
    fig7.show()
In [26]:
    # === Insight exemple ===
    print("\n🔎 Insight :")
    print("La majorité des offres viennent de :", df["source"].value_counts().idxmax())
    print("Nombre d'offres avec salaire renseigné :", len(df_salaire))
🔎 Insight :
La majorité des offres viennent de : AI-jobs.net
Nombre d'offres avec salaire renseigné : 4
In [30]:
    # === Insights supplémentaires ===
    print("\n🔎 Insights supplémentaires :")
    print("1. Nombre total d'offres :", len(df))
    print("2. Nombre d'entreprises uniques :", df["company"].nunique())
    print("3. Nombre de villes uniques :", df["location"].nunique())
    print("4. Intitulé de poste le plus fréquent :", df["title"].mode()[0])
    print("5. Entreprise ayant posté le plus d'offres :", df["company"].value_counts().idxmax())
    print("6. Salaire moyen max :", df["salary_max"].dropna().mean())
    print("7. Salaire médian min :", df["salary_min"].dropna().median())
    print("8. Pourcentage d'offres avec salaire renseigné :", round(df["has_salary"].mean() * 100, 2), "%")
    print("9. Nombre d'intitulés de poste uniques :", df["title"].nunique())
    print("10. Répartition API vs Scraping :")
    print(df["source"].value_counts(normalize=True) * 100)
🔎 Insights supplémentaires :
1. Nombre total d'offres : 100
2. Nombre d'entreprises uniques : 71
3. Nombre de villes uniques : 89
4. Intitulé de poste le plus fréquent : formation data science
5. Entreprise ayant posté le plus d'offres : DATASCIENTEST
6. Salaire moyen max : 1802.0
7. Salaire médian min : 486.0
8. Pourcentage d'offres avec salaire renseigné : 4.0 %
9. Nombre d'intitulés de poste uniques : 71
10. Répartition API vs Scraping :
source
AI-jobs.net    50.0
Adzuna API     50.0
Name: proportion, dtype: float64
In [ ]: